#ifndef __MY_SOCK__
#define __MY_SOCK__

// 封装TCPsock相关操作接口
#include <iostream>
#include <string>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include "log.hpp"

namespace QiHai
{
    struct Sock
    {
        // 创建套接字  错误返回-1，这里会输出错误日志，注意佩戴上log.hpp文件，编译加-DMYFILE选项日志输入到当前目录的文件中，否则就是标准输出
        static int socket()
        {
            int sock = ::socket(AF_INET, SOCK_STREAM, 0);
            if (0 > sock)  // ::表示使用全局命名空间的，不属于任何一个类或者命名空间 SOCK_STREAM是字节流-即TCP协议
            {
                logMessage(FATAL, "socket error-%d: %s[file:%s|line:%d]", errno, strerror(errno), __FILE__, __LINE__);
            }
            else logMessage(DEBUG, "socket success, sock is %d", sock);

            return sock;
        }

        // 设置对此资源设置非阻塞IO,出错会设置致命出错日志
        static bool setNonBlock(int sock)
        {
            int fl = fcntl(sock, F_GETFL);  // 获取当前sock底层的读写标记位 - 保持当前的不变
            if (fl > 0)
            {
                int flag = fcntl(sock, F_SETFL, fl | O_NONBLOCK);  // 在不动原来的基础上进行一个非阻塞
                if (flag != -1) return true;
            }
            // 这里都是出错了
            logMessage(FATAL, "fcntl error %d:%s", errno, strerror(errno));
            return false;
        }

        // 绑定ip和端口
        static bool bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
        {
            // 一般使用bind是服务器，所以，这里设置服务器主动取消四次挥手后的等待time_wait状态
            int opt = 1;  // 表示打开
            setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof opt);  // 在绑定前设置好

            struct sockaddr_in address;
            socklen_t  n;
            memset(&address, 0, sizeof address);  // 清零
            address.sin_family = AF_INET;
            address.sin_port = htons(port);  // 主机转网络
            inet_aton(ip.c_str(), &address.sin_addr);  // 点分十进制转化为数，然后转化为网络字节序
            if (0 > ::bind(sock, (sockaddr*)&address, sizeof(address)))
            {
                // 差错处理
                logMessage(FATAL, "bind error-%d: %s[file:%s|line:%d]", errno, strerror(errno), __FILE__, __LINE__);
                return false;
            }
            return true;
        }

        // 服务端设置监听
        static bool listen(int sock, int backlog = 20)  // 挂起队列最大长度默认设置为20
        {
            if (0 < ::listen(sock, backlog))
            {
                logMessage(FATAL, "listen error-%d: %s[file:%s|line:%d]", errno, strerror(errno), __FILE__, __LINE__);
                return false;
            }
            logMessage(DEBUG, "listen success, init......");
            return true;  // 设置此套接字为监听状态成功
        }

        // 服务器进行连接,ip和port为输出型参数。如果连接失败会打出日志，返回值为-1.注意连接失败不是致命的。连接成功也会打出日志
        static int accept(int listenSock, int* err_num, std::string* clientIp = nullptr, uint16_t* clientPort = nullptr)
        {
            // 想要客户端的ip和port为可选项
            struct sockaddr_in client;
            memset(&client, 0, sizeof client);
            socklen_t addrlen = sizeof client;
            int clientSock = ::accept(listenSock, (sockaddr*)&client, &addrlen);
            if (0 > clientSock)
            {
                logMessage(ERROR, "accept error-%d: %s[file:%s|line:%d]", errno, strerror(errno), __FILE__, __LINE__);
                // 错误但是不致命，可以进行重连哦
                *err_num = errno;
                return clientSock;
            }
            std::string ip = inet_ntoa(client.sin_addr);
            uint16_t port = ntohs(client.sin_port);
            logMessage(DEBUG, "[%s: %hu] accept success...", ip.c_str(), port);
            // 如果指针不为空就设置客户端返回的数据
            if (clientIp)
            {
                *clientIp = ip;  // 网络字节序-本机-数字-点分十进制
            }
            if (clientPort)
            {
                *clientPort = port;  // 网络-本机
            }
            return clientSock;
        }

        // 客户端进行连接
        static bool connect(int sock, const std::string& ip, uint16_t port)
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof server);
            server.sin_family = AF_INET;
            server.sin_port = htons(port);
            inet_aton(ip.c_str(), &server.sin_addr);

            if (0 > ::connect(sock, (sockaddr*)&server, sizeof server))
            {
                logMessage(FATAL, "connect error-%d: %s[file:%s|line:%d]", errno, strerror(errno), __FILE__, __LINE__);
                return false;
            }
            return true;
        }
    };
}

#endif